當API規模慢慢擴大,Unit test變得很重要,可以幫助我們檢查原本已經正常的功能,當開發新Feature的時候,可能改寫function,導致我們沒注意到的地方產生錯誤,原本寫好的Unit test就能幫我們找出該錯誤,今天會分別撰寫Controller, Service及Repository的Test,那麼接下來就開始吧.
$ sail artisan make:test PostRepositoryTest --unit
createPost()
$title, $content
/**
 * 建立文章
 *
 * @param string $title 標題
 * @param string $content 內文
 * @return mixed
 */
public function createPost(string $title, string $content)
{
    $user = Auth::guard('api')->user();
    $post = new Post();
    $post->title = $title;
    $post->content = $content;
    $post->user_id = $user->id;
    $post->save();
    return $post;
}
phpunit.xml把這兩行原本註解拿掉,我們要另外用另一個資料庫去測試,才不會影響到我們原本資料庫的資料,記得重啟container!<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
use HasFactory
# database/factories/UserFactory
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'account' => $this->faker->unique()->safeEmail(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'enabled' => 1,
        ];
    }
}
use RefreshDatabase用來清空資料庫的資料,確保每次測試資料不被資料庫數據影響<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tymon\JWTAuth\JWTGuard;
use Mockery;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Repositories\PostRepository;
class PostRepositoryTest extends TestCase
{
    use RefreshDatabase;
    /**
     * @var PostRepository
     */
    protected $post_repository;
    /**
     * 在每個 test case 開始前執行.
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->post_repository = app()->make(PostRepository::class);
        $this->user = User::factory()->create();
        $this->guard_mock = Mockery::mock(JWTGuard::class);
        Auth::shouldReceive('guard')
            ->with('api')
            ->andReturn($this->guard_mock);
        $this->guard_mock->shouldReceive('user')
            ->andReturn($this->user);
    }
    /**
     * 測試 成功建立文章
     */
    public function testCreatePostShouldSuccess()
    {
        $title = "測試標題";
        $content = "測試內文";
        $this->post_repository->createPost($title, $content);
        $this->assertDatabaseHas('posts', [
            'user_id' => $this->user->id,
            'title' => $title,
            'content' => $content,
        ]);
    }
}
$ sail artisan make:test PostServiceTest --unit
create()
$data
/**
 * 建立文章
 * @param array $data
 * @return mixed
 */
public function create(array $data)
{
    $title = Arr::get($data, 'title');
    $content = Arr::get($data, 'content');
    $post = $this->post_repository->createPost($title, $content);
    return $post;
}
use HasFactory
# database/factories/PostFactory
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->word(),
            'content' => $this->faker->word(),
            'user_id' => User::factory()->create()->id,
        ];
    }
}
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Mockery;
use App\Services\PostService;
use App\Repositories\PostRepository;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostServiceTest extends TestCase
{
    use RefreshDatabase;
    /**
     * @var PostRepository
     */
    protected $post_repository_mock;
    /**
     * @var PostService
     */
    protected $post_service;
    /**
     * 在每個 test case 開始前執行.
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->post_repository_mock = Mockery::mock(PostRepository::class);
        $this->post_service = new PostService($this->post_repository_mock);
    }
    /**
     * 測試 建立文章Service處理成功
     */
    public function testCreatePostSuccess()
    {
        $post = Post::factory()->create();
        $fake_input = [
            'title' => '測試標題',
            'content' => '測試內文',
        ];
        $this->post_repository_mock
            ->shouldReceive('createPost')
            ->once()
            ->andReturn($post);
        $actual_result = $this->post_service->create($fake_input);
        $this->assertEquals($post->title, $actual_result['title']);
        $this->assertEquals($post->content, $actual_result['content']);
        $this->assertEquals($post->user_id, $actual_result['user_id']);
    }
}
$ sail artisan make:test PostControllerTest
# routes/api.php
Route::group(['prefix' => 'auth'], function () {
    Route::post('login', [AuthController::class, 'login'])->name('auth.login');
    Route::post('register', [AuthController::class, 'register'])->name('auth.register');
});
Route::middleware(['jwt.auth'])->group(function () {
    Route::group(['prefix' => 'user'], function () {
        Route::get('/', [UserController::class, 'index'])->name('user.index');
    });
    Route::group(['prefix' => 'post'], function () {
        Route::post('/', [PostController::class, 'create'])->name('post.create');
        Route::get('/', [PostController::class, 'index'])->name('post.index');
    });
});
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tymon\JWTAuth\Facades\JWTAuth;
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
    /**
     * 產生 jwt 驗證 token
     *
     * @return mixed
     */
    protected function createToken($user)
    {
        $token = JWTAuth::fromUser($user);
        return 'Bearer '.$token;
    }
}
createToken()藉此產生token<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use App\Models\User;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
    use RefreshDatabase;
    /**
     * @var API Header
     */
    protected $header = [
        'X-Requested-With' => 'XMLHttpRequest',
        'Content-Type'     => 'application/json',
    ];
    public function setUp(): void
    {
        parent::setUp();
        $user = User::factory()->create();
        $this->header["Authorization"] = $this->createToken($user);
    }
    /**
     * 測試 建立文章成功
     */
    public function testCreatePostSuccess()
    {
        $fake_data = [
            'title' => '測試標題',
            'content' => '測試內文',
        ];
        $response = $this->withHeaders($this->header)->postJson(Route('post.create'), $fake_data)->decodeResponseJson();
        $this->assertTrue($response['title'] == $fake_data['title']);
        $this->assertTrue($response['content'] == $fake_data['content']);
    }
}
$ sail test
